import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Scanner;

/*** class commands - contains an instance of Simulation which deals with an array of
robots. Command class is in charge of receiving commands, identifying them and acting accordingly ***/
 
/*ADDING OR REMOVING COMMANDS: write a function that deals with command and check for it in 
 							   runCommand function using String's matches method*/
public class Commands 
{		
	 //simulation class is in charge of a group of robots
	 private Simulation sim;
	 //hash map of commands
	 private HashMap<String, Command> commandsMap;
	 //boxes
	 private Boxes boxes;
	 //programs class - in charge of running robot's programs in separate threads
	 private Programs programs;
	 
	 
	 //constructor
	 public Commands(Simulation s)
	  {
		 commandsMap = new HashMap<String,Command>();
		 //add all types of commands to map
		 add(new InitCommand());
		 add(new SimulateCommand());
		 add(new GetPositionCommand());
		 add(new ListRobotsCommand());
		 add(new FilesCommand());
		 add(new LoadCommand());
		 add(new AssignCommand());
		 add(new PutBoxCommand());
		 add(new ExitCommand());
		 
		 sim = s;
		 boxes = new Boxes();
		 programs = new Programs();
	  }
	 
	 //adds command to commands map
	 private void add(Command c){
		  commandsMap.put(c.getKey(), c);
	 }
	
	//function prints appropriate error when wrong robot name is entered
	public void printErrorWrongName() {System.out.println("Error: no such robot with this name.");}

	//receive string of command and act accordingly
	public void runCommand(String command_str)
	{ 	
		String cmdName = Main.firstWord(command_str);

		//check if command exists in map
		if(commandsMap.containsKey(cmdName)){
			Command command = commandsMap.get(cmdName); //receive command class
			
			if(command.checkCommand(command_str)){  //check if command is in correct format
				command.execute(command_str); //execute command
				return; //end command
			}
		}
		//incorrect command
		System.out.println("Error: command not found.");
	}
	 
	/** abstract class of all commands **/
	private abstract class Command{
			protected String format;
			public abstract void execute(String cmd);
			public abstract String getKey();
			//function checks if entered command matches format
			public boolean checkCommand(String cmd){
				if(format.matches(cmd))
					return true;
				return false;
				//return (format.matches(cmd));
			 }
	}
	
	/***___________________________________________________________________________***/
	/** Init Command class - inserting new robot **/
	private class InitCommand extends Command{
		String format2; 
		public InitCommand(){
			format = "Init type (\\w+) at (-?\\d+) (-?\\d+) named (\\w+)";
			format2 = "Init type (\\w+) at (-?\\d+) (-?\\d+) named (\\w+) with (\\w+)";
		 }
		 //returns true if command matches one of the formats
		 public boolean checkCommand(String cmd){
			 return(format.matches(cmd) || format2.matches(cmd));
		 }
		 //checks which format of the 2 it is given it matches one of them
		 private short whichFormat(String cmd){
			 if(format.matches(cmd))
				 return 1;
			 else return 2;
		 }
		 // return the first word of the command as the key
		 public String getKey(){ return "Init";}
		 
		 //function executes received command
		 public void execute(String cmd){
			 Scanner scn = new Scanner(cmd);
			 scn.findInLine(format); // scans according to the format
		
			 // get the variables
			 String type = scn.match().group(1);
			 int x = Integer.parseInt(scn.match().group(2));//start x 
			 int y = Integer.parseInt(scn.match().group(3));//start y 
			 String name = scn.match().group(4);
			 
			//if entered robot type exists
			 if(sim.TypeExists(type)){	
				 
				 //find out if there are any extra additions
				 short formatNumber = whichFormat(cmd);
				 if(formatNumber == 1){
					 sim.insertRobot(type, name, x, y);
					 System.out.println( "new " + type + " at " + x + "," + y + " named " + name);
				 }
				 else{
					 String addition = scn.match().group(5);
				 	 InitWhithAddition(type, name, x, y, addition);
			 	 }
			 }
			 else{
				 System.out.println("Error: no such robot type.");
			 }
		}//end execute function
		
		//creates new robot with addition  [arm | smartKit | all]
		private void InitWhithAddition(String type, String name, int x, int y, String addition){
			//if addition is arm
			if(addition.equals("arm") == true)
				sim.initWithArm(type,name,x,y);
			
			else if(addition.equals("smartKit") == true)
				sim.initWithSmartKit(type,name,x,y);
			
			else if(addition.equals("all"))
				sim.initWithAll(type,name,x,y);
			
			else{
				System.out.println("Error: no such addition.");
				return;
			}
			System.out.format("new Robot with %s of %s at %ld,%ld named %s",addition,type,x,y,name);
		}
	}//end class InitCommand
	
	/***________________________________________________________________________________***/
	/** Simulate Command class - runs robot's programs x amoungt of seconds **/
	private class SimulateCommand extends Command{
		
		 public SimulateCommand(){format = "Simulate (\\d+) seconds" ;}
	
		 // return the first word of the command as the key
		 public String getKey(){ return "Simulate";}
		 //function executes received command
		 public void execute(String cmd){ 
			 Scanner scn = new Scanner(cmd);
			 scn.findInLine(format); // scans according to the format
			
			 // get the variables
			 int seconds = Integer.parseInt(scn.match().group(1));
			//run all robots with files
			 programs.run(seconds);
		}
	}//end class SimulateCommand
	
	/***___________________________________________________________________________***/
	/*** GetPOsition command - returns robots current position ***/
	private class GetPositionCommand extends Command{
		 public GetPositionCommand(){ format = "Get (\\w+) position";}
		
		 // return the first word of the command as the key
		 public String getKey(){ return "Get";}
		
		 public void execute(String cmd){
				 Scanner scn = new Scanner(cmd);
				 scn.findInLine(format);   // scans according to the format
				
				 String name = scn.match().group(1); //get robot name
				 //if robot exists in simulation
				 if(sim.NameExists(name)){ 	   
					 Position p = sim.getRobot(name).getCurrentPosition();	//get robots position
					 System.out.println("robot " + name + " is at " + p.toString());
				 }
				 else if(Boxes.exists(name))
				 {
					 Position p = Boxes.getBox(name).getPosition();	//get box position
					 System.out.println("box " + name + " is at " + p.toString());
				 }
				 else{
					 System.out.println("Error: no such robot or box with this name.");;
				 }
		 }
	 }//end class GetPosition command
	
	/***___________________________________________________________________________***/
	/*** ListType interface and all types of lists are used for ListRobots command class ***/
	
	//interface ListType - different ways to list robots
	 private interface ListType{
		 public void execute();
		 public String getKey();
	 }
	 
	 //list type Distance - lists robots according to distance from start
	 private class DistanceList implements ListType{
		 //type of listing is the key
		 public String getKey(){ return "distance";}
		 //list robots according to their distance from starting point
		 public void execute(){
			 	//print list of robots by distance
			 	sim.printByDistance();
			}
	 }
	 
	 //list type name - lists robots according to distance from start
	 private class NameList implements ListType{
		 //type of listing is the key
		 public String getKey(){ return "name";}
		 //list robots according to their distance from starting point
		 public void execute(){
			 //print list of robots by name
			 sim.printByName();
		 }
	 }
	
	 
	 /***___________________________________________________________________________***/
	 /**class ListRobot Command - lists robots in different ways according to request**/
	private class ListRobotsCommand extends Command{
		 //hash map of list types
		 HashMap<String,ListType> ListTypes;
		 
		 //c'tor
		 public ListRobotsCommand(){
			 format = "List robots by (\\w+)"; //format for main commands map
			 ListTypes = new HashMap<String, ListType>();
			 add(new DistanceList());
			 add(new NameList());
		 }
		 
		 //adds List type to types map  
		 private void add(ListType type){
			 ListTypes.put(type.getKey(), type);
		 }
		 
		 // return the first word of the command as the key to main commands map
		 public String getKey(){ return "List";}
		
		 public void execute(String cmd){
			 Scanner scn = new Scanner(cmd);
			 scn.findInLine(format);   // scans according to the format
			
			 String listType = scn.match().group(1); //get type of list (name/distance)
			 
			 //if type of listing exists in listType map then list robots
			 if(ListTypes.containsKey(listType)){
				 ListTypes.get(listType).execute();
			 }
		 }
	}//end class ListRobots command
	
	
	/*** Files command__________________________________________________________ ***/
	private class FilesCommand extends Command{
		 public FilesCommand() { format = "Files"; }
		 // return the first word of the command as the key
		 public String getKey(){ return "Files";}
		
		 public void execute(String cmd){
			
			  String[] filenames;
	          File f = new File(".");
	          filenames = f.list();
	          for(int i=0; i< filenames.length; i++){
	        	  //if directory
	        	  if(new File(filenames[i]).isDirectory()){
	        		  System.out.print("\t[d] ");
	        	  }
	        	  else
	        		  System.out.print("\t[f] ");
	          
	          System.out.println(filenames[i]);
			 }
		 }
			
		 
	 }//end class Files command
	
	
	/*** Load command_________________________________________________________________ ***/
	private class LoadCommand extends Command{
		
		 public LoadCommand() { format = "Load (\\w.+)"; }
		 // return the first word of the command as the key
		 public String getKey(){ return "Load";}
		
		 public void execute(String cmd){
				  Scanner scn = new Scanner(cmd);
				  scn.findInLine(format);
				  
				  String name = scn.match().group(1); //get name of file
				  try{
					  BufferedReader br = new BufferedReader( new FileReader(name));
					  String line;
					  //read line from file
					  while((line = br.readLine())!=null){
						  System.out.println( ">> " + line); //print command from file
						  runCommand(line); //execute command
					  }
				  }
				  catch(FileNotFoundException e){System.out.println("Error: file not found.");}
				  catch(IOException e){System.out.println(e.getMessage());}
		 }
	}//end class Load command
	
	/*** Assigns robot to program ___________________________________________________***/
	private class AssignCommand extends Command{
		 public AssignCommand(){ format = "Assign (\\w+) with (\\w.+)";}
		
		 // return the first word of the command as the key
		 public String getKey(){ return "Assign";}
		
		 public void execute(String cmd){
				 Scanner scn = new Scanner(cmd);
				 scn.findInLine(format);   // scans according to the format
				
				 String robot_name = scn.match().group(1); //get robot name
				 String file_name = scn.match().group(2); //get file name
				 //if robot exists in simulation
				 if(sim.NameExists(robot_name)){ 	   
					//check if file exists in system
					 File f = new File("." + file_name);
					 
					 if(f.exists() && f.isFile())
						 programs.add(new RunProgram(sim.getRobot(robot_name), file_name));
					 else
						 System.out.println("Error: file not found.");
				 }
				 else
					 printErrorWrongName();
			}//end execute
		}//end class Assign command
	
	/*** place a box in a certain position ___________________________________________________***/
	private class PutBoxCommand extends Command{
		 public PutBoxCommand(){ format = "PutBox at (-?\\d+) (-?\\d+) named (\\w+)";}
		
		 // return the first word of the command as the key
		 public String getKey(){ return "Put";}
		
		 public void execute(String cmd){
				 Scanner scn = new Scanner(cmd);
				 scn.findInLine(format);   // scans according to the format
				
				 int x = Integer.parseInt(scn.match().group(1)); //get x
				 int y = Integer.parseInt(scn.match().group(2)); //get y
				 String box_name = scn.match().group(3);			 //get box name
				 
				 //if box exists in Boxes
				 if(Boxes.exists(box_name))
					 System.out.println("Box by that name allready exists");
				 
				 else
					 boxes.addBox(new Box(box_name, new Position(x,y)));
			}//end execute
		}//end class put box command
	
	/*** exit simulation ____________________________________________________________________ ***/
	private class ExitCommand extends Command{
		 public ExitCommand() { format = "Exit"; }
		
		 // return the first word of the command as the key
		 public String getKey(){ return "Exit";}
	
		 public void execute(String cmd){
			 
				 System.out.println("bye.");
				 System.exit(0);
			 }
	}//end Exit command

}//end of Commands class


/*	Move command - sets robots speed and heading 
	private class MoveCommand extends Command{
		
		 public MoveCommand(){format = "Move (\\w+) speed (-?\\d+) heading (-?\\d+)";}
	
		 // return the first word of the command as the key
		 public String getKey(){ return "Move";}
		 
		 //function executes received command
		 public void execute(String cmd){
			
				 Scanner scn = new Scanner(cmd);
				 scn.findInLine(format);// scans according to the format
		
				 // get the variables
				 String name = scn.match().group(1);
				 int speed = Integer.parseInt(scn.match().group(2));
				 int heading = Integer.parseInt(scn.match().group(3));
				 //check if robot with this name exists in simulation
				 if(sim.NameExists(name)){
					 sim.getRobot(name).move(speed, heading);
				 }
				 else{
					 printErrorWrongName();
				 }
		 }//end of execute function
	}//end class moveCommand*/

	

